package ja.lingo.application.gui.marks;

import ja.centre.gui.actionbinder.ActionBinder;
import ja.centre.gui.util.IGui;
import ja.lingo.application.gui.main.MainPanel;
import ja.lingo.application.model.Model;
import ja.lingo.engine.Engine;
import ja.lingo.engine.IEngine;
import ja.lingo.engine.beans.Word;
import ja.lingo.engine.beans.marks.IMark;
import ja.lingo.engine.beans.marks.MarkHelper;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MarkBGui implements IGui {
    private Integer TYPE = IMark.MARK_B;

    private IEngine engine;
    private Model model;
    private JTaskPanel gui;

    class JTaskPanel extends JPanel implements MouseMotionListener, MouseListener {
        private Point mousePoint;
        private double bWidth = .40;
        private double bBetwen = (1 - 2 * bWidth) * .4;
        private double bStart = (1 - bWidth * 2 - bBetwen) / 2;
        private int selX = -1;
        private int selY = -1;

        private List<Word> words;
        private String[][] wordValues;
        private List<Word> checkedWords = new ArrayList<Word>();

        int h, w, x1, x2;

        public JTaskPanel(LayoutManager layout) {
            super(layout);
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        private void drawCentred(Graphics2D g2, String text, float px,
                                 float py, boolean selected, boolean checked, String additionalInfo) {
            Font prevFont = g2.getFont();

            if (checked) {
                g2.setColor(Color.lightGray);
            } else {
                g2.setColor(selected ? Color.white : Color.black);
            }
            Rectangle2D bounds;
            while (true) {
                FontRenderContext frc = g2.getFontRenderContext();
                Font font = g2.getFont();
                bounds = font.getStringBounds(text, frc);
                if (bounds.getWidth() > w) {
                    g2.setFont(new Font("Arial", Font.PLAIN, g2.getFont().getSize() - 2));
                } else {
                    break;
                }
            }

            float pw = (float) bounds.getWidth();
            float ph = (float) bounds.getHeight();
            float x = (float) ((px + w / 2) - bounds.getX() - pw / 2);
            float y = (float) ((py + h / 2) - bounds.getY() - ph / 2);

            g2.drawString(text + (additionalInfo != null ? " - " + additionalInfo : ""), x, y);
            g2.setFont(prevFont);
        }

        private void drawRec(Graphics2D g2, int x, int y, int w, int h,
                             boolean selected, boolean checked) {
            if (checked) {
                g2.setColor(gui.getBackground());
            } else {
                g2.setColor(isMouseInArea(x, y, w, h) ? Color.lightGray : gui.getBackground());
            }
            if (selected) {
                g2.setColor(Color.black);
            }
            g2.fillRect(x, y, w, h);

            g2.setColor(checked ? Color.lightGray : Color.gray);
            g2.drawRect(x, y, w, h);
        }

        private boolean isMouseInArea(int x, int y, int w, int h) {
            if (mousePoint != null) {
                int mX = (int) mousePoint.getX();
                int mY = (int) mousePoint.getY();
                return (mX >= x && mX <= x + w && mY >= y && mY <= y + h);
            }
            return false;
        }

        private boolean wordIsCheked(String word) {
            for (Word w : checkedWords) {
                if (word.equalsIgnoreCase(w.getValue())
                        || word.equalsIgnoreCase(w.getTranslated())) {
                    return true;
                }
            }
            return false;
        }

        private int[] getItem(int x, int y) {
            int selCol = -1;
            int selRow = -1;

            int i = y / h;
            if (i % 2 == 1) {
                selRow = i / 2;
            }
            if (x >= x1 && x <= x1 + w) {
                selCol = 0;
            } else if (x >= x2 && x <= x2 + w) {
                selCol = 1;
            }

            return new int[]{selCol, selRow};
        }

        private String getString(int x, int y) {
            int[] sel = getItem(x, y);
            if (sel[0] != -1 && sel[1] != -1) {
                return wordValues[sel[0]][sel[1]];
            }
            return null;
        }

        private Word getWord(String value) {
            for (Word w : getWords()) {
                if (w.getValue().equals(value)) {
                    return w;
                }
            }
            return null;
        }

        private Font getFontForHeight(Graphics2D g2, int height) {
            Font f;
            int i = 1;
            do {
                i++;
                f = new Font("Arial", Font.PLAIN, i);
            } while (g2.getFontMetrics(f).getHeight() < height);
            return f;
        }

        public void paint(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setColor(gui.getBackground());
            g2.fillRect(0, 0, getWidth(), getHeight());
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            h = getHeight() / (getWords().size() * 2 + 1);
            w = (int) (getWidth() * bWidth);
            x1 = (int) (getWidth() * bStart);
            x2 = x1 + w + (int) (getWidth() * bBetwen);

            g2.setFont(getFontForHeight(g2, h));

            int y = h;
            for (int k = 0; k < wordValues[0].length; k++) {
                String value1 = wordValues[0][k];
                String value2 = wordValues[1][k];
                Word word = getWord(value1);

                boolean checked1 = wordIsCheked(value1);
                boolean checked2 = wordIsCheked(value2);
                drawRec(g2, x1, y, w, h, selX == 0 && selY == k, checked1);
                drawRec(g2, x2, y, w, h, selX == 1 && selY == k, checked2);

                IMark mark = MarkHelper.getMark(TYPE, word);
                drawCentred(g2, value1, x1, y, selX == 0 && selY == k, checked1,
                        "" + mark.getValue());
                drawCentred(g2, value2, x2, y, selX == 1 && selY == k, checked2, null);
                y += h * 2;
            }
        }

        private void good(Word w) {
            MarkHelper.increase(TYPE, w);
        }

        private void wrong(Word w) {
            MarkHelper.decrease(TYPE, w);
        }

        public void doCheck(int col1, int row1, int col2, int row2) {
            Word w = getWord(wordValues[0][col1 == 0 ? row1 : row2]);
            String translated = wordValues[1][col1 == 0 ? row2 : row1];
            if (w != null && w.getTranslated().equalsIgnoreCase(translated)) {
                good(w);
                checkedWords.add(w);
                selX = -1;
                selY = -1;
                if (checkedWords.size() >= getWords().size()) {
                    setWords(getWords());
                    checkedWords.clear();
                }
                repaint();
            } else {
                wrong(w);
            }
        }

        public void mouseDragged(MouseEvent e) {
            mousePoint = e.getPoint();
            repaint();
        }

        public void mouseMoved(MouseEvent e) {
            mousePoint = e.getPoint();
            repaint();
        }

        public void mouseClicked(MouseEvent e) {
            mousePoint = e.getPoint();
            String s = getString(e.getX(), e.getY());
            if (s != null && !wordIsCheked(s)) {
                int[] sel = getItem((int) e.getPoint().getX(), (int) e.getPoint().getY());
                if (selX != -1 && selY != -1 && sel[0] != -1 && sel[1] != -1 && sel[0] != selX) {
                    doCheck(selX, selY, sel[0], sel[1]);
                } else {
                    selX = sel[0];
                    selY = sel[1];
                }
                repaint();
            } else {
                selX = -1;
                selY = -1;
                repaint();
            }
        }

        public void mousePressed(MouseEvent e) {
        }

        public void mouseReleased(MouseEvent e) {
        }

        public void mouseEntered(MouseEvent e) {
        }

        public void mouseExited(MouseEvent e) {
        }

        public List<Word> getWords() {
            return words;
        }

        public int[] getRandomVector(int size) {
            Set<Integer> busyPos = new HashSet<Integer>();
            int[] result = new int[size];
            for (int i = 0; i < size; i++) {
                int pos;
                while (busyPos.contains(pos = (int) ((size * Math.random())))) {
                }
                result[i] = pos;
                busyPos.add(pos);
            }
            return result;
        }

        public void setWords(List<Word> newWords) {
            int size = newWords.size();
            wordValues = new String[2][size];

            int[] rPos1 = getRandomVector(size);
            int[] rPos2 = getRandomVector(size);
            for (int i = 0; i < size; i++) {
                wordValues[0][i] = newWords.get(rPos1[i]).getValue();
                wordValues[1][i] = newWords.get(rPos2[i]).getTranslated();
            }

            this.words = newWords;
        }
    }

    public void setWordsToCheck(List<Word> wordsToCheck) {
        gui.setWords(wordsToCheck);
    }

    public MarkBGui(Model model, Engine engine, MainPanel mainPanel) {
        this.engine = engine;
        this.model = model;

        gui = new JTaskPanel(new BorderLayout());

        ActionBinder.bind(this);
    }

    public JComponent getGui() {
        return gui;
    }
}
